<?php

namespace paymch\models;

use Yii;
use paymch\components\wechat\HttpDataFormatter;
use common\models\trade\Trade;
use common\models\trade\TradeOrder;
use common\helpers\Utils;

/**
 * WechatPayment is the model behind the contact form.
 * @
 */
class WechatRefund extends BasePaymentModel {

    /**
     * 返回状态码
     * @name $return_code
     * @var string(16) SUCCESS/FAIL 此字段是通信标识，非交易标识，交易是否成功需要查看result_code来判断
     * @example SUCCESS
     * @static 是
     */
    public $return_code;

    /**
     * 返回信息
     * @name $return_msg
     * @var string(128) 返回信息，如非空，为错误原因
     * @example 签名失败 参数格式校验错误
     * @static 否
     */
    public $return_msg;

    /**
     * 应用Id
     * @name $appid
     * @var string(32) 微信开放平台审核通过的应用APPID
     * @example wx8888888888888888
     * @static 是
     */
    public $appid;

    /**
     * 商户号
     * @name $mch_id
     * @var string(32) 微信支付分配的商户号
     * @example 1900000109
     * @static 是
     */
    public $mch_id;

    /**
     * 随机字符串
     * @name $nonce_str
     * @var string(32) 随机字符串，不长于32位
     * @example 5K8264ILTKCH16CQ2502SI8ZNMTM67VS
     * @static 是
     */
    public $nonce_str;

    /**
     * 加密信息
     * @name $req_info
     * @var string(1024) 加密信息请用商户秘钥进行解密，详见解密方式
     * https://pay.weixin.qq.com/wiki/doc/api/app/app.php?chapter=9_16&index=10#menu1
     * @example 5K8264ILTKCH16CQ2502SI8ZNMTM67VS
     * @static 是
     */
    public $req_info;

    /**
     * ====================================
     * 以下参数为加密信息参数
     * ====================================
     */

    /**
     * 微信订单号
     * @name $transaction_id
     * @var string(32) 微信订单号 
     * @example 4200000015201711144695436073
     * @static 是
     */
    public $transaction_id;

    /**
     * 商户订单号
     * @name $out_trade_no
     * @var string(32) 商户系统内部订单号，要求32个字符内，只能是数字、大小写字母_-|*@ ，且在同一个商户号下唯一。
     * @example 401171114753563715
     * @static 是
     */
    public $out_trade_no;

    /**
     * 微信退款单号
     * @name $refund_id
     * @var string(32) 微信退款单号
     * @example 50000105032017112802499148377
     * @static 是
     */
    public $refund_id;

    /**
     * 商户退款单号
     * @name $out_refund_no
     * @var string(64) 商户系统内部的退款单号，商户系统内部唯一，只能是数字、大小写字母_-|*@ ，同一退款单号多次请求只退一笔。
     * @example 7768R401171114753563715
     * @static 是
     */
    public $out_refund_no;

    /**
     * 订单金额
     * @name $total_fee
     * @var Int 订单总金额，单位为分，只能为整数，详见支付金额
     * @example 100
     * @static 是
     */
    public $total_fee;

    /**
     * 应结订单金额
     * @name $settlement_total_fee
     * @var Int 当该订单有使用非充值券时，返回此字段。应结订单金额=订单金额-非充值代金券金额，应结订单金额<=订单金额。 
     * @example 100
     * @static 否
     */
    public $settlement_total_fee;

    /**
     * 申请退款金额
     * @name $refund_fee
     * @var Int 退款总金额,单位为分 
     * @example 100
     * @static 是
     */
    public $refund_fee;

    /**
     * 退款金额
     * @name $settlement_refund_fee
     * @var Int 退款金额=申请退款金额-非充值代金券退款金额，退款金额<=申请退款金额 
     * @example 100
     * @static 是
     */
    public $settlement_refund_fee;

    /**
     * 退款状态
     * @name $refund_status
     * @var string(16) SUCCESS-退款成功；CHANGE-退款异常；REFUNDCLOSE—退款关闭 
     * @example SUCCESS
     * @static 是
     */
    public $refund_status;

    /**
     * 退款成功时间
     * @name $success_time
     * @var datetime
     * @example 2017-11-28 18:16:04
     * @static 否
     */
    public $success_time;

    /**
     * 退款入账账户
     * @name $refund_recv_accout
     * @var String(64) 
     * 取当前退款单的退款入账方
     * 1）退回银行卡：{银行名称}{卡类型}{卡尾号}
     * 2）退回支付用户零钱:支付用户零钱
     * 3）退还商户:商户基本账户
     * 商户结算银行账户
     * 4）退回支付用户零钱通:支付用户零钱通 
     * @example 招商银行信用卡0403
     * @static 是
     */
    public $refund_recv_accout;

    /**
     * 退款资金来源
     * @name $refund_account
     * @var String(30) REFUND_SOURCE_RECHARGE_FUNDS 可用余额退款/基本账户；REFUND_SOURCE_UNSETTLED_FUNDS 未结算资金退款
     * @example REFUND_SOURCE_RECHARGE_FUNDS
     * @static 是
     */
    public $refund_account;

    /**
     * 退款发起来源
     * @name $refund_request_source
     * @var String(30) API（接口）|VENDOR_PLATFORM（商户平台）
     * @example API
     * @static 是
     */
    public $refund_request_source;

    /**
     * 交易支付订单模型
     * @var type $_trade
     */
    private $_trade = null;

    /**
     * @inheritdoc
     *
     * 1、商户需要验证该通知数据中的out_trade_no是否为商户系统中创建的订单号，
     * 2、判断total_amount是否确实为该订单的实际金额（即商户订单创建时的金额），
     * 3、校验通知中的seller_id（或者seller_email) 是否为out_trade_no这笔单据的对应的操作方（有的时候，一个商户可能有多个seller_id/seller_email），
     * 4、验证app_id是否为该商户本身。
     * 上述1、2、3、4有任何一个验证不通过，则表明本次通知是异常通知，务必忽略。
     * 
     */
    public function rules() {
        return [
            ['return_code', 'required'],
            //校验return_code
            ['return_code', 'validateReturnCode'],
            //required
            [
                ['appid', 'mch_id', 'nonce_str', 'req_info'], 'required'
            ],
            //size
            [['appid', 'mch_id', 'nonce_str'], 'string', 'max' => 32],
            [['req_info'], 'string'],
            //校验appid,mch_id
            [['appid', 'mch_id'], 'validateConfigs'],
            //校验支付回调通知状态
            ['req_info', 'validateRequestInfo'],
            //req_info 解析正确时验证
            [
                [
                    'transaction_id',
                    'out_trade_no',
                    'refund_id',
                    'out_refund_no',
                    'total_fee',
                    'refund_fee',
                    'settlement_refund_fee',
                    'refund_status',
                    'refund_recv_accout',
                    'refund_account',
                    'refund_request_source'
                ],
                'required'
            ],
            [['transaction_id', 'out_trade_no', 'refund_id'], 'string', 'max' => 32],
            [['out_refund_no', 'refund_recv_accout'], 'string', 'max' => 64],
            [['total_fee', 'settlement_total_fee', 'refund_fee', 'settlement_refund_fee'], 'number'],
            [['refund_status'], 'string', 'max' => 16],
            [['success_time'], 'string', 'max' => 20],
            [['success_time'], 'date', 'format' => 'yyyy-MM-dd HH:mm:ss'],
            [['refund_account', 'refund_request_source'], 'string', 'max' => 30],
            //校验商户订单号
            ['out_refund_no', 'validateOutRefundNo'],
            //校验订单支付金额
            ['refund_fee', 'validateRefundFee'],
        ];
    }

    /**
     * @inheritdoc
     */
    public function attributeLabels() {
        return [
            'appid' => '应用ID',
            'mch_id' => '商户号',
            'nonce_str' => '随机字符串',
            'req_info' => '加密信息',
            //
            'transaction_id' => '微信订单号',
            'out_trade_no' => '商户订单号',
            'refund_id' => '微信退款单号',
            'out_refund_no' => '商户退款单号',
            'total_fee' => '订单金额',
            'settlement_total_fee' => '应结订单金额',
            'refund_fee' => '申请退款金额',
            'settlement_refund_fee' => '退款金额',
            'refund_status' => '退款状态',
            'success_time' => '退款成功时间',
            'refund_recv_accout' => '退款入账账户',
            'refund_account' => '退款资金来源',
            'refund_request_source' => '退款发起来源',
        ];
    }

    /**
     * Validates the return_code.
     *
     * @param string $attribute the attribute currently being validated
     */
    public function validateReturnCode($attribute) {
        if (!$this->hasErrors()) {
            if ($this->$attribute !== 'SUCCESS') {
                $this->addError($attribute, $this->return_msg ?: '通信失败！');
            }
        }
    }

    /**
     * Validates the appid or mch_id.
     * 验证appid或者mch_id是否为该商户本身
     *
     * @param string $attribute the attribute currently being validated
     */
    public function validateConfigs($attribute) {
        if (!$this->hasErrors()) {
            if ($this->$attribute !== Yii::$app->params['wechat'][$attribute]) {
                $this->addError($attribute, '非当前应用的支付商户！');
            }
        }
    }

    /**
     * Validates the Request Info.
     * @param string $attribute the attribute currently being validated
     */
    public function validateRequestInfo($attribute) {
        if (!$this->hasErrors()) {
            $request = $this->decryptData($this->$attribute, Yii::$app->params['wechat']['partner_key']);
            if ($request == false) {
                $this->addError($attribute, '加密信息解密失败！');
            } else {
                $requestData = HttpDataFormatter::fromXml($request);
                $this->setAttributes($requestData);
            }
        }
    }

    /**
     * Validates the out_refund_no.
     * 商户需要验证该通知数据中的out_refund_no是否为商户系统中创建的订单号
     *
     * @param string $attribute the attribute currently being validated
     */
    public function validateOutRefundNo($attribute) {
        if (!$this->hasErrors()) {
            $trade = $this->getTradeOrder();
            if (!$trade) {
                $this->addError($attribute, '退款申请的商户订单信息不存在！');
            }
        }
    }

    /**
     * Validates the refund_fee.
     * 判断total_amount是否确实为该订单的实际金额（即商户订单创建时的金额）
     *
     * @param string $attribute the attribute currently being validated
     * @param array $params the additional name-value pairs given in the rule
     */
    public function validateRefundFee($attribute, $params) {
        if (!$this->hasErrors()) {
            $trade = $this->getTradeOrder();
            if ($trade) {
                parent::invokeLog('[' . $attribute . ']TradeOrder', $trade->attributes);
                parent::invokeLog('[' . $attribute . ']This', $this->attributes);
                if (strval($trade->pay * 100) !== $this->refund_fee) {
                    $this->addError($attribute, '退款订单的金额与实际退款申请金额不一致！[' . strval($trade->pay * 100) . '-' . $this->refund_fee . ']');
                }
            } else {
                $this->addError($attribute, '退款申请的商户订单信息不存在！');
            }
        }
    }

    protected function decryptData($encryptData, $key = '') {
        //1. base64_decode
        $data = base64_decode($encryptData);
        //2. md5 original key
        $partnerKey = strtolower(md5($key));
        //3. decrypt AES ECB
        return openssl_decrypt($data, "AES-256-ECB", $partnerKey, OPENSSL_RAW_DATA);
    }

    /**
     * Finds trade by [[trade_no]]
     *
     * @return Trade|null
     */
    protected function getTradeOrder() {
        if ($this->_trade === null) {
            $this->_trade = TradeOrder::find()->alias('t')->joinWith('trade trade')
            ->where([
                'trade.trade_no' => $this->out_refund_no,
                't.paymode' => TradeOrder::PAYMODE_WECHAT,
                't.balance' => TradeOrder::BALANCE_REFUND,
            ])
            ->one();
            parent::invokeLog('[' . $this->out_refund_no . ']Refund TradeOrder', $this->_trade->attributes);
        }
        return $this->_trade;
    }

    /**
     * 在上述验证通过后商户必须根据支付宝不同类型的业务通知，正确的进行不同的业务处理，并且过滤重复的通知结果数据。
     * 在支付宝的业务通知中，只有交易通知状态为TRADE_SUCCESS或TRADE_FINISHED时，支付宝才会认定为买家付款成功。
     * 
     * @param string $email the target email address
     * @return bool whether the email was sent
     */
    public function save(&$msg) {
        if (!$this->validate()) {
            return false;
        }
        $trade = $this->_trade;
        if (!$trade) {
            $msg = '退款交易订单不存在！';
            return false;
        }
        $trade->paymode = TradeOrder::PAYMODE_WECHAT;
        $trade->pay_type = $this->refund_request_source;
        $trade->pay_no = $this->refund_id;
        $trade->pay_time = (string) strtotime($this->success_time);
        $trade->status = TradeOrder::STATUS_SUCCESS;

        if (empty($trade->dirtyAttributes)) {
            return $trade->trade_id;
        }

        if (!$trade->save()) {
            $msg = '交易保存时发生系统内部错误' . Utils::arrayToString($trade->firstErrors);
            return false;
        }

        return $trade->trade_id;
    }

}
